/* *************************************** */
/* * PPM Functions Library               * */
/* * Authors:                            * */
/* *  André Bannwart Perina      7152757 * */
/* *  Rodrigo de Freitas Pereira 7573472 * */
/* *  Thaís Emanuele dos Santos  6453087 * */
/* *************************************** */

#include "../include/ppm_funcs.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ** Create a PPM Standard Segment ***************************************************************** */
/* * Description: Creates a PPM Standard Segment. This segment is allocated a double pointer        * */
/* *              scheme, being easily resizable                                                    * */
/* * Inputs: - seg: Segment pointer to receive created segment                                      * */
/* * Outputs: PPM_ERR_MALLOC_ERROR if malloc fails, or PPM_SUCCESS if no errors were found          * */
/* * Compatibility: PPM Standard Segment                                                            * */
/* ************************************************************************************************** */
int ppm_create(ppm_seg_t **seg) {
	/* Initialise stuff */
	*seg = malloc(sizeof(ppm_seg_t));
	(*seg)->m = (*seg)->n = -1;
	(*seg)->d = NULL;

	/* Malloc error */
	if(NULL == *seg)
		return PPM_ERR_MALLOC_ERROR;

	return PPM_SUCCESS;
}

/* ** Destroy a PPM Standard Segment **************************************************************** */
/* * Description: Destroys a PPM Standard Segment                                                   * */
/* * Inputs: - seg: Segment to be destroyed                                                         * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Standard Segment                                                            * */
/* ************************************************************************************************** */
int ppm_destroy(ppm_seg_t **seg) {
	int i;

	/* If segment is not empty, free data */
	if((*seg)->m != -1 && (*seg)->n != -1) {
		for(i = 0; i < (*seg)->m; i++)
			free((*seg)->d[i]);
		free((*seg)->d);
	}

	/* Free segment */
	free(*seg);

	return PPM_SUCCESS;
}

/* ** Destroy a PPM Referenced Segment ************************************************************** */
/* * Description: Destroys a PPM Referenced Segment                                                 * */
/* * Inputs: - seg: Segment to be destroyed                                                         * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Referenced Segment                                                          * */
/* ************************************************************************************************** */
int ppm_destroy_ref(ppm_seg_t **seg) {
	/* Free segment */
	free(*seg);

	return PPM_SUCCESS;
}

/* ** Destroy a PPM Constant Segment **************************************************************** */
/* * Description: Destroys a PPM Constant Segment                                                   * */
/* * Inputs: - seg: Segment to be destroyed                                                         * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Constant Segment                                                            * */
/* ************************************************************************************************** */
int ppm_destroy_const(ppm_seg_t **seg) {
	/* If segment is not empty, free linear data */
	if((*seg)->m != -1 && (*seg)->n != -1) {
		free((*seg)->d[0]);
		free((*seg)->d);
	}

	/* Free Segment */
	free(*seg);

	return PPM_SUCCESS;
}

/* ** Clear a PPM Standard Segment ****************************************************************** */
/* * Description: Clears a PPM Standard Segment. It is equivalent to destroying and creating a      * */
/* *              segment                                                                           * */
/* * Inputs: - seg: Segment to be erased                                                            * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Standard Segment                                                            * */
/* ************************************************************************************************** */
int ppm_clear(ppm_seg_t *seg) {
	int i;

	/* Free data if segment is not empty */
	if(seg->m != -1 && seg->n != -1) {
		for(i = 0; i < seg->m; i++)
			free(seg->d[i]);
		free(seg->d);
		seg->d = NULL;
		seg->m = seg->n = -1;
	}

	return PPM_SUCCESS;	
}

/* ** Clear a PPM Referenced Segment **************************************************************** */
/* * Description: Clears (de-references) a PPM Referenced Segment. It is equivalent to destroying   * */
/* *              and creating a segment                                                            * */
/* * Inputs: - seg: Segment to be erased                                                            * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Referenced Segment                                                          * */
/* ************************************************************************************************** */
int ppm_clear_ref(ppm_seg_t *seg) {
	/* Unref data if segment is not empty */
	if(seg->m != -1 && seg->n != -1) {
		free(seg->d);
		seg->d = NULL;
		seg->m = seg->n = -1;
	}

	return PPM_SUCCESS;	
}

/* ** Clear a PPM Constant Segment ****************************************************************** */
/* * Description: Clears a PPM Constant Segment. It is equivalent to destroying and creating a      * */
/* *              segment                                                                           * */
/* * Inputs: - seg: Segment to be erased                                                            * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Constant Segment                                                            * */
/* ************************************************************************************************** */
int ppm_clear_const(ppm_seg_t *seg) {
	/* Free linear data if segment is not empty */
	if(seg->m != -1 && seg->n != -1) {
		free(seg->d[0]);
		free(seg->d);
		seg->d = NULL;
		seg->m = seg->n = -1;
	}

	return PPM_SUCCESS;	
}

/* ** Set a PPM Standard Segment ******************************************************************** */
/* * Description: Set a PPM Standard Segment according to an input matrix. The data is copied.      * */
/* * Inputs: - d: Input Matrix Data                                                                 * */
/* *         - seg: Segment to be fulfilled                                                         * */
/* *         - m: Number of lines present in d                                                      * */
/* *         - n: Number of columns present in d                                                    * */
/* * Outputs: PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard Segment                                                            * */
/* ************************************************************************************************** */
int ppm_set(byte **d, ppm_seg_t *seg, unsigned int m, unsigned int n) {
	int i, j;

	/* If segment is not empty, abort */
	if(seg->m != -1 || seg->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Allocate segment */
	seg->d = malloc(m * sizeof(byte *));
	for(i = 0; i < m; i++)
		seg->d[i] = malloc(n);

	/* Set size */
	seg->m = m;
	seg->n = n;

	/* Copy data */
	for(i = 0; i < m; i++)
		for(j = 0; j < n; j++)
			seg->d[i][j] = d[i][j];

	return PPM_SUCCESS;
}

/* ** Copy a PPM Standard Segment ******************************************************************* */
/* * Description: Copy a PPM Standard Segment according to another PPM Segment                      * */
/* * Inputs: - input: Input Segment                                                                 * */
/* *         - output: Output Segment                                                               * */
/* * Outputs: PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard, Referenced or Constant (input), PPM Standard Segment (output)     * */            
/* ************************************************************************************************** */
int ppm_copy(ppm_seg_t *input, ppm_seg_t *output) {
	int i, j;

	/* If segment is not empty, abort */
	if(output->m != -1 || output->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Allocate segment */
	output->d = malloc(input->m * sizeof(byte *));
	for(i = 0; i < input->m; i++)
		output->d[i] = malloc(input->n);

	/* Set size */
	output->m = input->m;
	output->n = input->n;

	/* Copy data */
	for(i = 0; i < output->m; i++)
		for(j = 0; j < output->n; j++)
			output->d[i][j] = input->d[i][j];

	return PPM_SUCCESS;
}

/* ** Get PPM Size from a File ********************************************************************** */
/* * Description: Ditto                                                                             * */
/* * Inputs: - fileName: File Name                                                                  * */
/* *         - format: PPM Format (PPM_FORMAT_P3 or PPM_FORMAT_P6)                                  * */
/* *         - m: Reference to integer where number of lines will be stored                         * */
/* *         - n: Reference to integer where number of columns will be stored                       * */
/* * Outputs: PPM_ERR_INVALID_FILE if no file was opened, PPM_ERR_INVALID_FORMAT if format was not  * */
/* *          as specified or PPM_SUCCESS if no errors were found                                   * */
/* ************************************************************************************************** */
int ppm_get_size(char *fileName, unsigned int format, unsigned int *m, unsigned int *n) {
	FILE *ipf = fopen(fileName, "r");
	char gc;
	int gi;

	/* Failed to open file */
	if(NULL == ipf)
		return PPM_ERR_INVALID_FILE;

	/* Get header */
	gc = fgetc(ipf);
	if(gc != 'P') {
		fclose(ipf);
		return PPM_ERR_INVALID_FORMAT;
	}

	/* Get format */
	fscanf(ipf, "%d", &gi);
	if(gi != format) {
		fclose(ipf);
		return PPM_ERR_INVALID_FORMAT;
	}

	/* Read width and height */
	fscanf(ipf, "%d", n);
	fscanf(ipf, "%d", m);

	/* Close file */
	fclose(ipf);

	return PPM_SUCCESS;
}

/* ** Set a PPM Standard Segment from a File ******************************************************** */
/* * Description: Reads a channel from a file and stores in a PPM Standard Segment                  * */
/* * Inputs: - fileName: File Name                                                                  * */
/* *         - channel: Colour Channel (PPM_FORMAT_R, PPM_FORMAT_G or PPM_FORMAT_B)                 * */
/* *         - format: PPM Format (PPM_FORMAT_P3 or PPM_FORMAT_P6)                                  * */
/* *         - seg: Segment to be fulfilled                                                         * */
/* * Outputs: PPM_ERR_INVALID_FILE if no file was opened, PPM_ERR_INVALID_FORMAT if format was not  * */
/* *          as specified, PPM_ERR_SEG_EXISTS if segment has already data,                         * */
/* *          PPM_ERR_INVALID_CHANNEL_PARAMETER if specified channel is invalid,                    * */
/* *          PPM_ERR_INVALID_MAXVAL if maximum value is different from 255,                        * */
/* *          PPM_ERR_INVALID_FORMAT_PARAMETER if format parameter differs from file format or      * */
/* *          PPM_SUCCESS if no errors were found                                                   * */
/* * Compatibility: PPM Standard Segment                                                            * */            
/* ************************************************************************************************** */
int ppm_get_channel(char *fileName, unsigned int channel, unsigned int format, ppm_seg_t *seg) {
	FILE *ipf = fopen(fileName, "r");
	char gc;
	int gi, i, j, k;

	/* Failed to open file */
	if(NULL == ipf)
		return PPM_ERR_INVALID_FILE;

	/* If segment is not empty, abort */
	if(seg->m != -1 || seg->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Invalid channel parameter */
	if(channel < 0 || channel > 2)
		return PPM_ERR_INVALID_CHANNEL_PARAMETER;

	/* Get header */
	gc = fgetc(ipf);
	if(gc != 'P') {
		fclose(ipf);
		return PPM_ERR_INVALID_FORMAT;
	}

	/* Get format */
	fscanf(ipf, "%d", &gi);
	if(gi != format) {
		fclose(ipf);
		return PPM_ERR_INVALID_FORMAT;
	}

	/* Read width and height */
	fscanf(ipf, "%d", &(seg->n));
	fscanf(ipf, "%d", &(seg->m));

	/* Allocate segment */
	seg->d = malloc(seg->m * sizeof(byte *));
	for(i = 0; i < seg->m; i++)
		seg->d[i] = malloc(seg->n);

	/* Get max value */
	fscanf(ipf, "%d", &gi);
	if(gi != 255) {
		fclose(ipf);
		for(i = 0; i < seg->m; i++)
			free(seg->d[i]);
		free(seg->d);
		seg->m = seg->n = -1;

		return PPM_ERR_INVALID_MAXVAL;
	}

	/* Jump newline */
	fseek(ipf, 1, SEEK_CUR);

	if(PPM_FORMAT_P3 == format) {
		/* Read data */
		for(i = 0; i < seg->m; i++) {
			/* Skip unwanted channel data */
			for(k = 0; k < channel; k++)
				fscanf(ipf, "%d", &gi);

			for(j = 0; j < seg->n; j++) {
				/* Get value */
				fscanf(ipf, "%d", &gi);
				if(gi > 255) {
					fclose(ipf);
					for(i = 0; i < seg->m; i++)
						free(seg->d[i]);
					free(seg->d);
					seg->m = seg->n = -1;

					return PPM_ERR_INVALID_FORMAT;
				}

				seg->d[i][j] = gi;

				/* Skip unwanted channel data */
				switch(channel) {
					case PPM_CHANNEL_R:
						fscanf(ipf, "%d", &gi);
						fscanf(ipf, "%d", &gi);
						break;
					case PPM_CHANNEL_G:
						fscanf(ipf, "%d", &gi);
						if(j < seg->n - 1) 
							fscanf(ipf, "%d", &gi);
						break;
					case PPM_CHANNEL_B:
						if(j < seg->n - 1) {
							fscanf(ipf, "%d", &gi);
							fscanf(ipf, "%d", &gi);
						}
						break;
				}
			}
		}
	}
	else if(PPM_FORMAT_P6 == format) {
		/* Skip unwanted channel data */
		for(k = 0; k < channel; k++)
			fgetc(ipf);

		/* Get data */
		for(i = 0; i < seg->m; i++) {
			for(j = 0; j < seg->n; j++) {

				seg->d[i][j] = fgetc(ipf);

				fgetc(ipf);
				fgetc(ipf);
			}
		}
	}
	/* Invalid format */
	else {
		fclose(ipf);
		for(i = 0; i < seg->m; i++)
			free(seg->d[i]);
		free(seg->d);
		seg->m = seg->n = -1;

		return PPM_ERR_INVALID_FORMAT_PARAMETER	;
	}

	/* Close file */
	fclose(ipf);

	return PPM_SUCCESS;
}

/* ** Set a PPM Constant Segment from a File ******************************************************** */
/* * Description: Reads a channel from a file and stores in a PPM Constant Segment                  * */
/* * Inputs: - fileName: File Name                                                                  * */
/* *         - channel: Colour Channel (PPM_FORMAT_R, PPM_FORMAT_G or PPM_FORMAT_B)                 * */
/* *         - format: PPM Format (PPM_FORMAT_P3 or PPM_FORMAT_P6)                                  * */
/* *         - seg: Segment to be fulfilled                                                         * */
/* * Outputs: PPM_ERR_INVALID_FILE if no file was opened, PPM_ERR_INVALID_FORMAT if format was not  * */
/* *          as specified, PPM_ERR_SEG_EXISTS if segment has already data,                         * */
/* *          PPM_ERR_INVALID_CHANNEL_PARAMETER if specified channel is invalid,                    * */
/* *          PPM_ERR_INVALID_MAXVAL if maximum value is different from 255,                        * */
/* *          PPM_ERR_INVALID_FORMAT_PARAMETER if format parameter differs from file format or      * */
/* *          PPM_SUCCESS if no errors were found                                                   * */
/* * Compatibility: PPM Constant Segment                                                            * */            
/* ************************************************************************************************** */
int ppm_get_channel_const(char *fileName, unsigned int channel, unsigned int format, ppm_seg_t *seg) {
	FILE *ipf = fopen(fileName, "r");
	char gc;
	int gi, i, j, k;

	/* Failed to open file */
	if(NULL == ipf)
		return PPM_ERR_INVALID_FILE;

	/* If segment is not empty, abort */
	if(seg->m != -1 || seg->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Invalid channel parameter */
	if(channel < 0 || channel > 2)
		return PPM_ERR_INVALID_CHANNEL_PARAMETER;

	/* Get header */
	gc = fgetc(ipf);
	if(gc != 'P') {
		fclose(ipf);
		return PPM_ERR_INVALID_FORMAT;
	}

	/* Get format */
	fscanf(ipf, "%d", &gi);
	if(gi != format) {
		fclose(ipf);
		return PPM_ERR_INVALID_FORMAT;
	}

	/* Read width and height */
	fscanf(ipf, "%d", &(seg->n));
	fscanf(ipf, "%d", &(seg->m));

	/* Allocate segment */
	seg->d = malloc(seg->m * sizeof(byte *));
	seg->d[0] = malloc(seg->m * seg->n);
	for(i = 1; i < seg->m; i++)
		seg->d[i] = &(seg->d)[0][i * seg->n];

	/* Get max value */
	fscanf(ipf, "%d", &gi);
	if(gi != 255) {
		fclose(ipf);
		free(seg->d[0]);
		free(seg->d);
		seg->m = seg->n = -1;

		return PPM_ERR_INVALID_MAXVAL;
	}

	/* Jump newline */
	fseek(ipf, 1, SEEK_CUR);

	if(PPM_FORMAT_P3 == format) {
		/* Read data */
		for(i = 0; i < seg->m; i++) {
			/* Skip unwanted channel data */
			for(k = 0; k < channel; k++)
				fscanf(ipf, "%d", &gi);

			for(j = 0; j < seg->n; j++) {
				/* Get value */
				fscanf(ipf, "%d", &gi);
				if(gi > 255) {
					fclose(ipf);
					free(seg->d[0]);
					free(seg->d);
					seg->m = seg->n = -1;

					return PPM_ERR_INVALID_FORMAT;
				}

				seg->d[i][j] = gi;

				/* Skip unwanted channel data */
				switch(channel) {
					case PPM_CHANNEL_R:
						fscanf(ipf, "%d", &gi);
						fscanf(ipf, "%d", &gi);
						break;
					case PPM_CHANNEL_G:
						fscanf(ipf, "%d", &gi);
						if(j < seg->n - 1) 
							fscanf(ipf, "%d", &gi);
						break;
					case PPM_CHANNEL_B:
						if(j < seg->n - 1) {
							fscanf(ipf, "%d", &gi);
							fscanf(ipf, "%d", &gi);
						}
						break;
				}
			}
		}
	}
	else if(PPM_FORMAT_P6 == format) {
		/* Skip unwanted channel data */
		for(k = 0; k < channel; k++)
			fgetc(ipf);

		/* Get data */
		for(i = 0; i < seg->m; i++) {
			for(j = 0; j < seg->n; j++) {

				seg->d[i][j] = fgetc(ipf);

				fgetc(ipf);
				fgetc(ipf);
			}
		}
	}
	/* Invalid format */
	else {
		fclose(ipf);
		free(seg->d[0]);
		free(seg->d);
		seg->m = seg->n = -1;

		return PPM_ERR_INVALID_FORMAT_PARAMETER;
	}

	/* Close file */
	fclose(ipf);

	return PPM_SUCCESS;
}

/* ** Set a File from three PPM Segments ************************************************************ */
/* * Description: Saves a file using three PPM Segments, one for each colour channel                * */
/* * Inputs: - fileName: File Name                                                                  * */
/* *         - format: PPM Format (PPM_FORMAT_P3 or PPM_FORMAT_P6)                                  * */
/* *         - r: Red Channel Segment                                                               * */
/* *         - g: Green Channel Segment                                                             * */
/* *         - b: Blue Channel Segment                                                              * */
/* * Outputs: PPM_ERR_INVALID_FILE if no file was opened, PPM_ERR_CHANNEL_SIZES_MISMATCH if colour  * */
/* *          segments have different sizes, PPM_ERR_INVALID_FORMAT_PARAMETER if format parameter   * */
/* *          is invalid or PPM_SUCCESS if no errors were found                                     * */
/* * Compatibility: PPM Standard, Referenced or Constant Segment                                    * */            
/* ************************************************************************************************** */
int ppm_set_file(char *fileName, unsigned int format, ppm_seg_t *r, ppm_seg_t *g, ppm_seg_t *b) {
	FILE *opf = fopen(fileName, "w");
	int i, j;

	/* Failed to open file */
	if(NULL == opf)
		return PPM_ERR_INVALID_FILE;

	/* There is mismatch between channel sizes */
	if(r->m != g->m || r->m != b->m || g->m != b->m || r->n != g->n || r->n != b->n || g->n != b->n) {
		fclose(opf);
		return PPM_ERR_CHANNEL_SIZES_MISMATCH;
	}

	/* Write file P3 Format */
	if(PPM_FORMAT_P3 == format) {
		fprintf(opf, "P3\n%d %d\n%d\n", r->n, r->m, 255);

		for(i = 0; i < r->m; i++) {
			for(j = 0; j < r->n; j++)
				fprintf(opf, "%d %d %d ", r->d[i][j] & 0xff, g->d[i][j] & 0xff, b->d[i][j] & 0xff);

			fseek(opf, -1, SEEK_CUR);
			fputc('\n', opf);
		}
	}
	/* Write file P6 Format */
	else if(PPM_FORMAT_P6 == format) {
		fprintf(opf, "P6\n%d %d\n%d\n", r->n, r->m, 255);

		for(i = 0; i < r->m; i++)
			for(j = 0; j < r->n; j++)
				fprintf(opf, "%c%c%c", r->d[i][j], g->d[i][j], b->d[i][j]);
	}
	/* Invalid format */
	else {
		fclose(opf);
		return PPM_ERR_INVALID_FORMAT_PARAMETER;
	}

	/* Close file */
	fclose(opf);

	return PPM_SUCCESS;
}

/* ** Dump Channel Segments to a File *************************************************************** */
/* * Description: Dump colour channels to a file without specifying PPM header, useful for          * */
/* *              concatenation                                                                     * */
/* * Inputs: - fileName: File Name                                                                  * */
/* *         - format: PPM Format (PPM_FORMAT_P3 or PPM_FORMAT_P6)                                  * */
/* *         - r: Red Channel Segment                                                               * */
/* *         - g: Green Channel Segment                                                             * */
/* *         - b: Blue Channel Segment                                                              * */
/* * Outputs: PPM_ERR_INVALID_FILE if no file was opened, PPM_ERR_CHANNEL_SIZES_MISMATCH if colour  * */
/* *          segments have different sizes, PPM_ERR_INVALID_FORMAT_PARAMETER if format parameter   * */
/* *          is invalid or PPM_SUCCESS if no errors were found                                     * */
/* * Compatibility: PPM Standard, Referenced or Constant Segment                                    * */            
/* ************************************************************************************************** */
int ppm_file_dump(char *fileName, unsigned int format, ppm_seg_t *r, ppm_seg_t *g, ppm_seg_t *b) {
	FILE *opf = fopen(fileName, "w");
	int i, j;

	/* Failed to open file */
	if(NULL == opf)
		return PPM_ERR_INVALID_FILE;

	/* There is mismatch between channel sizes */
	if(r->m != g->m || r->m != b->m || g->m != b->m || r->n != g->n || r->n != b->n || g->n != b->n) {
		fclose(opf);
		return PPM_ERR_CHANNEL_SIZES_MISMATCH;
	}

	/* Write file P3 Format */
	if(PPM_FORMAT_P3 == format) {
		for(i = 0; i < r->m; i++) {
			for(j = 0; j < r->n; j++)
				fprintf(opf, "%d %d %d ", r->d[i][j] & 0xff, g->d[i][j] & 0xff, b->d[i][j] & 0xff);

			fseek(opf, -1, SEEK_CUR);
			fputc('\n', opf);
		}
	}
	/* Write file P6 Format */
	else if(PPM_FORMAT_P6 == format) {
		for(i = 0; i < r->m; i++)
			for(j = 0; j < r->n; j++)
				fprintf(opf, "%c%c%c", r->d[i][j], g->d[i][j], b->d[i][j]);
	}
	/* Invalid format */
	else {
		fclose(opf);
		return PPM_ERR_INVALID_FORMAT_PARAMETER;
	}

	/* Close file */
	fclose(opf);

	return PPM_SUCCESS;
}

/* ** Pack a Segment ******************************************************************************** */
/* * Description: Packs a Segment to a 1-dimensional contiguous array without size information      * */
/* * Inputs: - input: Segment to be packed                                                          * */
/* *         - output: Pointer to byte array that will received the packed segment                  * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Standard, Referenced or Constant Segment                                    * */            
/* ************************************************************************************************** */
int ppm_pack(ppm_seg_t *input, byte **output) {
	int i;
	*output = malloc(input->m * input->n);

	/* Linearise data matrix */
	for(i = 0; i < input->m; i++)
		memcpy(&(*output)[input->n * i], input->d[i], input->n);

	return PPM_SUCCESS;
}

/* ** Pack Multiple Segments ************************************************************************ */
/* * Description: Packs various Segments to a 1-dimensional contiguous array without size           * */
/* *              information. There is no physical indication of boundaries between segments       * */
/* * Inputs: - num: Number of Segments to be packed                                                 * */
/* *         - seg0, seg1, ... segn: All segments that will be packed. Should be equal to num       * */
/* *           segments                                                                             * */
/* *         - output: Pointer to byte array that will received all packed segments                 * */
/* * Outputs: PPM_SUCCESS                                                                           * */
/* * Compatibility: PPM Standard, Referenced or Constant Segment                                    * */ 
/* * Usage: ppm_pack_n(4, seg0, seg1, seg2, seg3, &output);                                         * */           
/* ************************************************************************************************** */
int ppm_pack_n(unsigned int num, ...) {
	va_list arguments;
	va_start(arguments, num);

	byte *out = NULL;
	int i, j, size = 0, oldSize;
	ppm_seg_t *t;

	/* Linearise all segments */
	for(i = 0; i < num; i++) {
		oldSize = size;
		t = va_arg(arguments, ppm_seg_t *);
		size += t->m * t->n;

		out = realloc(out, size);

		for(j = 0; j < t->m; j++)
			memcpy(&out[oldSize + (t->n * j)], t->d[j], t->n);
	}

	/* Last argument: Get output reference */
	byte **output = va_arg(arguments, byte **);

	*output = out;

	va_end(arguments);

	return PPM_SUCCESS;
}

/* ** Unpack a Segment ****************************************************************************** */
/* * Description: Unpacks a 1-dimensional contiguous array into a Segment.                          * */
/* * Inputs: - input: Byte Array that contains the segment                                          * */
/* *         - output: Segment to be fulfilled                                                      * */
/* *         - m: Number of lines                                                                   * */
/* *         - n: Number of columns                                                                 * */
/* * Outputs: PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard                                                                    * */            
/* ************************************************************************************************** */
int ppm_unpack(byte *input, ppm_seg_t *output, unsigned int m, unsigned int n) {
	int i;

	/* If segment is not empty, abort */
	if(output->m != -1 || output->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Get sizes */
	output->m = m;
	output->n = n;

	output->d = malloc(m * sizeof(byte *));

	/* Set segment */
	for(i = 0; i < m; i++) {
		output->d[i] = malloc(n);
		memcpy(output->d[i], &input[n * i], n);
	}

	return PPM_SUCCESS;
}

/* ** Unpack Multiple Segments ********************************************************************** */
/* * Description: Unpacks a 1-dimensional contiguous array into various Segments.                   * */
/* * Inputs: - num: Number of Segments to be unpacked                                               * */
/* *         - input: Byte Array that contain all segments                                          * */
/* *         - output0: First Segment to be fulfilled                                               * */
/* *         - n0: Number of lines for first segment                                                * */
/* *         - m0: Number of columns for first segment                                              * */
/* *         - ...                                                                                  * */
/* *         - outputn: Last Segment to be fulfilled                                                * */
/* *         - nn: Number of lines for last segment                                                 * */
/* *         - mn: Number of columns for last segment                                               * */
/* * Outputs: PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard                                                                    * */
/* * Usage: ppm_unpack_n(4, input, seg0, m0, n0, seg1, m1, n1, seg2, m2, n2, seg3, m3, n3);         * */             
/* ************************************************************************************************** */
int ppm_unpack_n(unsigned int num, ...) {
	va_list arguments;
	va_start(arguments, num);

	unsigned int i, m, n, size = 0;
	byte *input = va_arg(arguments, byte *);
	ppm_seg_t *t;

	/* Set all segments */
	for(i = 0; i < num; i++) {
		t = va_arg(arguments, ppm_seg_t *);
		m = va_arg(arguments, unsigned int);
		n = va_arg(arguments, unsigned int);

		/* Run ppm_unpack with gathered data */
		if(ppm_unpack(&input[size], t, m, n) != PPM_SUCCESS) {
			va_end(arguments);

			return PPM_ERR_SEG_EXISTS;
		}

		size += m * n;
	}

	va_end(arguments);

	return PPM_SUCCESS;
}

/* ** Get a Sub-Segment from a Segment ************************************************************** */
/* * Description: Gets a piece from a Segment                                                       * */
/* * Inputs: - input: Input Segment                                                                 * */
/* *         - output: Segment to receive the Sub-Segment from Input                                * */
/* *         - l: Line to start the Sub-Segment (inclusive)                                         * */
/* *         - c: Column to start the Sub-Segment (inclusive)                                       * */
/* *         - m: Number of lines from l                                                            * */
/* *         - n: Number of columns from c                                                          * */
/* * Outputs: PPM_ERR_INVALID_SEGMENT_PARAMETERS if any offset is bigger than the own segment,      * */
/* *          PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard, Reference or Constant (input), PPM Standard (output)              * */            
/* ************************************************************************************************** */
int ppm_get_segment(ppm_seg_t *input, ppm_seg_t *output, unsigned int l, unsigned int c, unsigned int m, unsigned int n) {
	int i, j;

	/* Invalid subsegment size */
	if(l + m > input->m || c + n > input->n)
		return PPM_ERR_INVALID_SEGMENT_PARAMETERS;

	/* If segment is not empty, abort */
	if(output->m != -1 || output->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Get sizes */
	output->m = m;
	output->n = n;

	/* Allocate segment */
	output->d = malloc(output->m * sizeof(byte *));
	for(i = 0; i < output->m; i++)
		output->d[i] = malloc(output->n);

	/* Get subsegment */
	for(i = 0; i < output->m; i++)
		for(j = 0; j < output->n; j++)
			output->d[i][j] = input->d[l+i][c+j];

	return PPM_SUCCESS;
}

/* ** Reference a Sub-Segment from a Segment ******************************************************** */
/* * Description: Gets a piece from a Segment and stores as reference on another Segment            * */
/* * Inputs: - input: Input Segment                                                                 * */
/* *         - output: Segment to receive the references from Sub-Segment from Input                * */
/* *         - l: Line to start the Sub-Segment (inclusive)                                         * */
/* *         - c: Column to start the Sub-Segment (inclusive)                                       * */
/* *         - m: Number of lines from l                                                            * */
/* *         - n: Number of columns from c                                                          * */
/* * Outputs: PPM_ERR_INVALID_SEGMENT_PARAMETERS if any offset is bigger than the own segment,      * */
/* *          PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard, Reference or Constant (input), PPM Reference (output). Please     * */  
/* *                note that recursive referencing may happen                                      * */          
/* ************************************************************************************************** */
int ppm_get_segment_ref(ppm_seg_t *input, ppm_seg_t *output, unsigned int l, unsigned int c, unsigned int m, unsigned int n) {
	int i;

	/* Invalid subsegment size */
	if(l + m > input->m || c + n > input->n)
		return PPM_ERR_INVALID_SEGMENT_PARAMETERS;

	/* If segment is not empty, abort */
	if(output->m != -1 || output->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Get sizes */
	output->m = m;
	output->n = n;

	/* Allocate reference array */
	output->d = malloc(output->m * sizeof(byte *));

	/* Get reference from desired lines */
	for(i = 0; i < output->m; i++)
		output->d[i] = &(input->d)[l+i][c];

	return PPM_SUCCESS;
}

/* ** Concatenate one Segment to Another ************************************************************ */
/* * Description: Concatenates one segment into another by copying. Segments must match number of   * */
/* *              lines for left and right orientations or number of columns for top or bottom      * */
/* * Inputs: - input: Segment to be concatenated                                                    * */
/* *         - output: Segment to receive the concatenation. If output is empty, this function will * */
/* *                   act as ppm_copy                                                              * */
/* *         - orientation: Concatenation Orientation (PPM_CONCAT_TOP, PPM_CONCAT_BOTTOM,           * */
/* *                        PPM_CONCAT_LEFT or PPM_CONCAT_RIGHT)                                    * */
/* * Outputs: PPM_ERR_INVALID_ORIENTATION_PARAMETER if orientation is invalid or PPM_SUCCESS if no  * */
/* *          errors were found                                                                     * */
/* * Compatibility: PPM Standard, Reference or Constant (input), PPM Standard (output)              * */       
/* ************************************************************************************************** */
int ppm_concat_segment(ppm_seg_t *input, ppm_seg_t *output, unsigned int orientation) {
	int i;

	/* If segment is not empty, abort */
	if(-1 == output->m && -1 == output->n) {
		ppm_copy(input, output);
		return PPM_SUCCESS;
	}

	switch(orientation) {
		/* Left concatenation */
		case PPM_CONCAT_LEFT:
			for(i = 0; i < output->m; i++) {
				/* Realloc each line */
				output->d[i] = realloc(output->d[i], output->n + input->n);
				/* Move data */
				memmove(&(output->d[i])[input->n], output->d[i], output->n);
				/* Concatenate */
				memcpy(output->d[i], input->d[i], input->n);
			}
			/* Refresh size */
			output->n += input->n;
			break;
		/* Right concatenation */
		case PPM_CONCAT_RIGHT:
			for(i = 0; i < output->m; i++) {
				/* Realloc each line */
				output->d[i] = realloc(output->d[i], output->n + input->n);
				/* Concatenate */
				memcpy(&(output->d[i])[output->n], input->d[i], input->n);
			}
			/* Refresh size */
			output->n += input->n;
			break;
		/* Top concatenation */
		case PPM_CONCAT_TOP:
			/* Realloc reference array */
			output->d = realloc(output->d, (output->m + input->m) * sizeof(byte *));
			/* Move data */
			memmove(&(output->d)[input->m], output->d, output->m * sizeof(byte *));
			/* Concatenate */
			for(i = 0; i < input->m; i++) {
				output->d[i] = malloc(output->n);
				memcpy(output->d[i], input->d[i], input->n);
			}
			/* Refresh size */
			output->m += input->m;
			break;
		/* Bottom concatenation */
		case PPM_CONCAT_BOTTOM:
			/* Realloc reference array */
			output->d = realloc(output->d, (output->m + input->m) * sizeof(byte *));
			/* Concatenate */
			for(i = 0; i < input->m; i++) {
				output->d[i + output->m] = malloc(output->n);
				memcpy(output->d[i + output->m], input->d[i], input->n);
			}
			/* Refresh size */
			output->m += input->m;
			break;
		/* Invalid orientation */
		default:
			return PPM_ERR_INVALID_ORIENTATION_PARAMETER;
	}

	return PPM_SUCCESS;
}

/* ** Add Black Border to Segment ******************************************************************* */
/* * Description: Adds a black border to a segment                                                  * */
/* * Inputs: - seg: Segment to receive the border                                                   * */
/* *         - border: Border Orientation (PPM_BORDER_TOP, PPM_BORDER_BOTTOM, PPM_BORDER_LEFT or    * */
/* *                   PPM_BORDER_RIGHT                                                             * */
/* *         - width: Border Size                                                                   * */
/* * Outputs: PPM_ERR_INVALID_WIDTH_PARAMETER if width is negative or zero,                         * */
/* *          PPM_ERR_INVALID_BORDER_PARAMETER if orientation is invalid or PPM_SUCCESS if no       * */
/* *          errors were found                                                                     * */
/* * Compatibility: PPM Standard                                                                    * */       
/* ************************************************************************************************** */
int ppm_add_border(ppm_seg_t *seg, unsigned int border, unsigned int width) {
	int i;

	/* Null or negative width */
	if(width <= 0)
		return PPM_ERR_INVALID_WIDTH_PARAMETER;

	switch(border) {
		/* Border on top */
		case PPM_BORDER_TOP:
			/* Realloc reference array */
			seg->d = realloc(seg->d, (seg->m + width) * sizeof(byte *));
			/* Move data */
			memmove(&(seg->d)[width], seg->d, seg->m * sizeof(byte *));
			/* Create border */
			for(i = 0; i < width; i++)
				seg->d[i] = calloc(seg->n, 1);
			/* Refresh size */
			seg->m += width;
			break;
		/* Border on bottom */
		case PPM_BORDER_BOTTOM:
			/* Realloc reference array */
			seg->d = realloc(seg->d, (seg->m + width) * sizeof(byte *));
			/* Create border */
			for(i = 0; i < width; i++)
				seg->d[seg->m + i] = calloc(seg->n, 1);
			/* Refresh size */
			seg->m += width;
			break;
		/* Border on left */
		case PPM_BORDER_LEFT:
			for(i = 0; i < seg->m; i++) {
				/* Realloc each line */
				seg->d[i] = realloc(seg->d[i], (seg->n + width));
				/* Move data */
				memmove(&(seg->d[i])[width], seg->d[i], seg->n);
				/* Create border */
				memset(seg->d[i], 0, width);
			}
			/* Refresh size */
			seg->n += width;
			break;
		/* Border on right */
		case PPM_BORDER_RIGHT:
			for(i = 0; i < seg->m; i++) {
				/* Realloc each line */
				seg->d[i] = realloc(seg->d[i], (seg->n + width));
				/* Create border */
				memset(&(seg->d[i])[seg->n], 0, width);
			}
			/* Refresh size */
			seg->n += width;
			break;
		/* Invalid orientation */
		default:
			return PPM_ERR_INVALID_BORDER_PARAMETER;
	}

	return PPM_SUCCESS;
}

/* ** Remove Border from Segment ******************************************************************** */
/* * Description: Removes a border from a segment                                                   * */
/* * Inputs: - seg: Segment to have the border removed                                              * */
/* *         - border: Border Orientation (PPM_BORDER_TOP, PPM_BORDER_BOTTOM, PPM_BORDER_LEFT or    * */
/* *                   PPM_BORDER_RIGHT                                                             * */
/* *         - width: Border Size                                                                   * */
/* * Outputs: PPM_ERR_INVALID_WIDTH_PARAMETER if width is negative or zero,                         * */
/* *          PPM_ERR_INVALID_BORDER_PARAMETER if orientation is invalid or PPM_SUCCESS if no       * */
/* *          errors were found                                                                     * */
/* * Compatibility: PPM Standard                                                                    * */       
/* ************************************************************************************************** */
int ppm_remove_border(ppm_seg_t *seg, unsigned int border, unsigned int width) {
	int i;

	/* Null or negative width */
	if(width <= 0)
		return PPM_ERR_INVALID_WIDTH_PARAMETER;

	switch(border) {
		/* Border on top */
		case PPM_BORDER_TOP:
			/* Refresh size */
			seg->m -= width;
			/* Free border */
			for(i = 0; i < width; i++)
				free(seg->d[i]);
			/* Move data */
			memmove(seg->d, &(seg->d)[width], seg->m * sizeof(byte *));
			/* Realloc reference array */
			seg->d = realloc(seg->d, seg->m * sizeof(byte *));
			break;
		/* Border on bottom */
		case PPM_BORDER_BOTTOM:
			/* Refresh size */
			seg->m -= width;
			/* Free border */
			for(i = 0; i < width; i++)
				free(seg->d[seg->m + i]);
			/* Realloc reference array */
			seg->d = realloc(seg->d, seg->m * sizeof(byte *));
			break;
		/* Border on left */
		case PPM_BORDER_LEFT:
			/* Refresh size */
			seg->n -= width;
			for(i = 0; i < seg->m; i++) {
				/* Move data */
				memmove(seg->d[i], &(seg->d[i])[width], seg->n);
				/* Realloc each line */
				seg->d[i] = realloc(seg->d[i], seg->n);
			}
			break;
		/* Border on right */
		case PPM_BORDER_RIGHT:
			/* Refresh size */
			seg->n -= width;
			for(i = 0; i < seg->m; i++)
				/* Realloc each line */
				seg->d[i] = realloc(seg->d[i], seg->n);
			break;
		/* Invalid orientation */
		default:
			return PPM_ERR_INVALID_BORDER_PARAMETER;
	}

	return PPM_SUCCESS;
}

/* ** Smooth Segment ******************************************************************************** */
/* * Description: Applies Smooth to a Segment using a 5x5 stencil. The first and last two columns   * */
/* *              and lines the smoothing is not applied                                            * */
/* * Inputs: - input: Segment to have the effect appllied                                           * */
/* *         - output: Output Segment                                                               * */
/* * Outputs: PPM_ERR_SEG_EXISTS if segment has already data or PPM_SUCCESS if no errors were found * */
/* * Compatibility: PPM Standard, Reference or Constant (input), PPM Standard (output)              * */       
/* ************************************************************************************************** */
int ppm_smooth(ppm_seg_t *input, ppm_seg_t *output) {
	int i, j;
	unsigned int val;

	/* If segment is not empty, abort */
	if(output->m != -1 || output->n != -1)
		return PPM_ERR_SEG_EXISTS;

	/* Set sizes */
	output->m = input->m;
	output->n = input->n;

	/* Allocate segment data */
	output->d = malloc(output->m * sizeof(byte *));
	for(i = 0; i < output->m; i++)
		output->d[i] = malloc(output->n);

	/* Calculate smooth */
	for(i = 2; i < input->m - 2; i++) {
		for(j = 2; j < input->n - 2; j++) {
			val =  ((input->d[i-2][j-2] & 0xff) +
					(input->d[i-2][j-1] & 0xff) +
					(input->d[i-2][j] & 0xff) +
					(input->d[i-2][j+1] & 0xff) +
					(input->d[i-2][j+2] & 0xff) +
					(input->d[i-1][j-2] & 0xff) +
					(input->d[i-1][j-1] & 0xff) +
					(input->d[i-1][j] & 0xff) +
					(input->d[i-1][j+1] & 0xff) +
					(input->d[i-1][j+2] & 0xff) +
					(input->d[i][j-2] & 0xff) +
					(input->d[i][j-1] & 0xff) +
					(input->d[i][j] & 0xff) +
					(input->d[i][j+1] & 0xff) +
					(input->d[i][j+2] & 0xff) +
					(input->d[i+1][j-2] & 0xff) +
					(input->d[i+1][j-1] & 0xff) +
					(input->d[i+1][j] & 0xff) +
					(input->d[i+1][j+1] & 0xff) +
					(input->d[i+1][j+2] & 0xff) +
					(input->d[i+2][j-2] & 0xff) +
					(input->d[i+2][j-1] & 0xff) +
					(input->d[i+2][j] & 0xff) +
					(input->d[i+2][j+1] & 0xff) +
					(input->d[i+2][j+2] & 0xff)) / 25;
			output->d[i][j] = val;
		}
	}

	return PPM_SUCCESS;
}